home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Mac Mania 5
/
MacMania 5.toast
/
/
Internet software
/
NewsWatcher
/
NW Source
/
Source
/
url.c
< prev
next >
Wrap
Text File
|
1997-01-09
|
35KB
|
1,373 lines
/*----------------------------------------------------------------------------
url.c
This module handles parsing and opening URLs.
Copyright © 1994-1997, Northwestern University.
----------------------------------------------------------------------------*/
#include <string.h>
#include <stdio.h>
#include <ctype.h>
#include <stdlib.h>
#include "glob.h"
#include "url.h"
#include "article.h"
#include "strutil.h"
#include "dialog.h"
#include "message.h"
#include "apputil.h"
#include "fileutil.h"
#include "newswatcher.h"
#include "memutil.h"
#include "resutil.h"
#include "sfutil.h"
#include "wind.h"
#include "full.h"
#include "subject.h"
#include "ic.h"
#define isurlschemechar(c) (isalnum((c)) || c == '+' || c == '.' || c == '-')
#define kOptionClickDlg 162
#define kCantFindHelperDlgID 153
#define kFetchCreatorType 'FTCh'
#define kFetchMinVersionNumber 0x02120000
#define kFetchMinVersionNumberStr "2.1.2"
#define kFetchMinVersionNumberGURL 0x03000000
#define kAnarchieCreatorType 'Arch'
#define kAnarchieMinVersionNumber 0x01200000
#define kAnarchieMinVersionNumberStr "1.2.0"
#define kAnarchieMinVersionNumberGURL 0x01402003
#define kFTPHelperURLFileType 'AURL'
#define kNetscapeCreatorType 'MOSS'
#define kNetscapeMinVersionNumber 0x00096003
#define kNetscapeMinVersionNumberStr "0.93 beta"
#define kMacWebCreatorType 'MWEB'
#define kMacWebMinVersionNumber 0x00000000
#define kMacWebMinVersionNumberStr ""
#define kMacWebMinVersionNumberGURL 0x01004003
#define kMacWAISCreatorType 'MWAS'
#define kMacWAISMinVersionNumber 0x01298000
#define kMacWAISMinVersionNumberStr "1.2.9"
#define kMacWebAndWAISOpenURLEventClass 'wwwc'
#define kMacWebAndWAISOpenURLEventID 'ourl'
#define kMacWebAndWAISOpenURLKeyword 'kURL'
#define kNCSAMosaicCreatorType 'MOS!'
#define kNCSAMosaicMinVersionNumber 0x02004008
#define kNCSAMosaicMinVersionNumberStr "2.0a8"
#define kNCSAMosaicOpenURLEventClass 'mos!'
#define kNCSAMosaicOpenURLEventID 'ourl'
#define kTurboGopherCreatorType 'TGOF'
#define kTurboGopherMinVersionNumber 0x02004001
#define kTurboGopherMinVersionNumberStr "2.0a1"
#define kTurboGopherMinVersionNumberCanon 0x02006001
#define kNCSATelnetCreatorType 'NCSA'
#define kNCSATelnetSettingsFileType 'CONF'
#define kNCSATelnetMinVersionNumber 0x02400000
#define kNCSATelnetMinVersionNumberStr "2.4"
#define kNCSATelnetMinVersionNumberGURL 0x02612004
#define ktn3270CreatorType 'GFTM'
#define ktn3270SettingsFileType 'GFTS'
#define ktn3270MinVersionNumber 0x02402007
#define ktn3270MinVersionNumberStr "2.4d7"
#define kFingerCreatorType 'PnLF'
#define kFingerMinVersionNumber 0x01500000
#define kFingerMinVersionNumberStr "1.5.0"
#define kPhCreatorType 'PHED'
#define kPhMinVersionNumber 0x01200000
#define kPhMinVersionNumberStr "1.2"
typedef enum TURLKind {
kNotURL,
kMailtoURL,
kNewsURL,
kNntpURL,
kOtherURL
} TURLKind;
typedef struct THelperInfo {
OSType sig; /* signature of helper program */
unsigned long minVersionNumber; /* min version number of helper program */
char *minVersionStr; /* min version number as a string */
} THelperInfo;
static THelperInfo gHelperInfo[] = {
{kAnarchieCreatorType, kAnarchieMinVersionNumber,
kAnarchieMinVersionNumberStr},
{kFetchCreatorType, kFetchMinVersionNumber,
kFetchMinVersionNumberStr},
{kNetscapeCreatorType, kNetscapeMinVersionNumber,
kNetscapeMinVersionNumberStr},
{kMacWebCreatorType, kMacWebMinVersionNumber,
kMacWebMinVersionNumberStr},
{kMacWAISCreatorType, kMacWAISMinVersionNumber,
kMacWAISMinVersionNumberStr},
{kNCSAMosaicCreatorType, kNCSAMosaicMinVersionNumber,
kNCSAMosaicMinVersionNumberStr},
{kTurboGopherCreatorType, kTurboGopherMinVersionNumber,
kTurboGopherMinVersionNumberStr},
{kNCSATelnetCreatorType, kNCSATelnetMinVersionNumber,
kNCSATelnetMinVersionNumberStr},
{ktn3270CreatorType, ktn3270MinVersionNumber,
ktn3270MinVersionNumberStr},
{kFingerCreatorType, kFingerMinVersionNumber,
kFingerMinVersionNumberStr},
{kPhCreatorType, kPhMinVersionNumber,
kPhMinVersionNumberStr},
{0, 0, nil}
};
typedef struct TDefaultHelperInfo {
TURLSchemeName schemeName;
OSType helpers[5];
} TDefaultHelperInfo;
static TDefaultHelperInfo gDefaultHelperInfo[] = {
{"ftp", {kFetchCreatorType, kAnarchieCreatorType, 0, 0, 0}},
{"http", {kMacWebCreatorType, kNetscapeCreatorType, kNCSAMosaicCreatorType, 0, 0}},
{"gopher", {kTurboGopherCreatorType, kNetscapeCreatorType, kMacWebCreatorType,
kNCSAMosaicCreatorType, 0}},
{"wais", {kMacWAISCreatorType, kNCSAMosaicCreatorType, 0, 0, 0}},
{"telnet", {kNCSATelnetCreatorType, 0, 0, 0, 0}},
{"tn3270", {ktn3270CreatorType, 0, 0, 0, 0}},
{"finger", {kFingerCreatorType, 0, 0, 0, 0}},
{"whois", {kFingerCreatorType, 0, 0, 0, 0}},
{"ph", {kPhCreatorType, 0, 0, 0, 0}},
{"", {0, 0, 0, 0, 0}},
};
/*----------------------------------------------------------------------------
CheckOneDefaultURLHelper
Check to see if we have a usable version of a default URL helper program.
Entry: sig = signature of helper program
Exit: function result = true if we have a usable version of this helper.
*versionNumber = version number of helper.
*lastMod = last mod date/time of helper
----------------------------------------------------------------------------*/
static Boolean CheckOneDefaultURLHelper (OSType sig, unsigned long *versionNumber,
unsigned long *lastMod)
{
THelperInfo *p;
OSErr err = noErr;
FSSpec fSpec;
/* Find the program on the user's disks. */
err = FindAppFromSig(sig, &fSpec, nil, nil);
if (err != noErr) return false;
/* Find the entry for this program in the gHelperInfo array. */
for (p = gHelperInfo; ; p++) {
if (p->sig == 0) return false;
if (p->sig == sig) break;
}
/* Check the version number. */
if (p->minVersionNumber != 0) {
err = GetAppVersionNumber(&fSpec, versionNumber);
if (err != noErr) return false;
if (*versionNumber < p->minVersionNumber) return false;
}
/* Get the last mod date and time. */
err = GetLastModDateTime(&fSpec, lastMod);
if (err != noErr) return false;
return true;
}
/*----------------------------------------------------------------------------
InitDefaultURLHelper
Initialize default URL helper info.
Entry: schemeName = pointer to scheme name.
----------------------------------------------------------------------------*/
void InitDefaultURLHelper (TURLSchemeName schemeName)
{
short i;
TURLHelperInfo *p;
TDefaultHelperInfo *q;
OSType *helpers, sig;
unsigned long versionNumber, lastMod;
/* Find the entry for this scheme in the gPrefs.urlHelpers array. If we can't
find the entry, or if a helper is already set for this scheme, return. */
for (i = 0, p = *gPrefs.urlHelpers; ; i++, p++) {
if (*p->schemeName == 0) return;
if (strcmp(schemeName, p->schemeName) == 0) break;
}
if (p->sig != 0) return;
/* Search the gDefaultHelperInfo array to find the list of helper sigs
for this scheme. */
for (q = gDefaultHelperInfo; ; q++) {
if (*q->schemeName == 0) return;
if (strcmp(schemeName, q->schemeName) == 0) break;
}
helpers = q->helpers;
/* For each sig in the list of helper sigs for this scheme, check to see if
the user has a copy of the program which is usable as a helper. If we find
one, set it in the gPrefs.urlHelpers array entry for this scheme. */
while (*helpers != 0) {
sig = *helpers;
helpers++;
if (CheckOneDefaultURLHelper(sig, &versionNumber, &lastMod)) {
p = &(*gPrefs.urlHelpers)[i];
p->sig = sig;
p->versionNumber = versionNumber;
p->lastMod = lastMod;
return;
}
}
}
/*----------------------------------------------------------------------------
ValidURLHelper
Check a file to see if it is a valid URL helper program.
Entry: fSpec = pointer to application file spec.
helperInfo = pointer to helper info.
Exit: function result = true if valid.
if the helper is valid:
helperInfo->sig = signature of helper.
helperInfo->versionNumber = version number of helper.
helperInfo->lastMod = last mod date/time of helper.
This function is called when the user selects a new URL helper in the
preferences dialog. It checks minimum version numbers for the known
URL helper programs. If the helper is valid, the helper info is filled
in. If the helper is not valid, an error message is issued.
This function is also recalled whenever the user runs a URL helper
and the last mod date/time of the helper as recorded in the helper info
does not match the last mod date/time of the file being executed.
----------------------------------------------------------------------------*/
Boolean ValidURLHelper (FSSpec *fSpec, TURLHelperInfo *helperInfo)
{
OSErr err = noErr;
FInfo fInfo;
OSType sig, sig2;
unsigned long versionNumber = 0;
unsigned long minVersionNumber = 0;
unsigned long lastMod;
short j;
CStr255 fmt, msg;
err = FSpGetFInfo(fSpec, &fInfo);
if (err != noErr) goto exit;
sig = fInfo.fdCreator;
for (j = 0; ; j++) {
sig2 = gHelperInfo[j].sig;
if (sig2 == 0 || sig == sig2) break;
}
if (sig2 != 0) minVersionNumber = gHelperInfo[j].minVersionNumber;
if (minVersionNumber != 0) {
err = GetAppVersionNumber(fSpec, &versionNumber);
if (err == resNotFound) return false;
if (err != noErr) goto exit;
if (versionNumber < minVersionNumber) {
GetCString(kStrHelperTooOld, fmt);
p2cstr(fSpec->name);
sprintf(msg, fmt, fSpec->name, gHelperInfo[j].minVersionStr);
c2pstr((char*)fSpec->name);
StopAlertMessage(msg);
return false;
}
}
err = GetLastModDateTime(fSpec, &lastMod);
if (err != noErr) goto exit;
helperInfo->sig = sig;
helperInfo->versionNumber = versionNumber;
helperInfo->lastMod = lastMod;
return true;
exit:
GetCString(kStrUnexpectedErr, fmt);
sprintf(msg, fmt, err);
StopAlertMessage(msg);
return false;
}
/*----------------------------------------------------------------------------
DoCantFindHelperDialog
Present a "can't find helper program" dialog.
Entry: msg = error message.
----------------------------------------------------------------------------*/
void DoCantFindHelperDialog (char *msg)
{
DialogPtr dlg;
short item;
OSErr err = noErr;
c2pstr(msg);
ParamText((StringPtr)msg, "\p", "\p", "\p");
p2cstr((StringPtr)msg);
err = MyGetNewDialog(kCantFindHelperDlgID, ok, 0, &dlg);
SysBeep(0);
if (err != noErr) return;
MyModalDialog(dlg, gDialogFilterUPP, &item);
DoClose(dlg);
}
/*----------------------------------------------------------------------------
HelperErrorMessage
Issue a helper program error message.
Entry: err = error number.
sig = helper program signature.
Exit: function result = userCanceledErr.
----------------------------------------------------------------------------*/
static OSErr HelperErrorMessage (OSErr err, OSType sig)
{
CStr255 fmt, msg;
Str31 name;
*name = 0;
FindAppNameFromSig(sig, name);
p2cstr(name);
if (err == fnfErr) {
if (*name == 0) {
GetCString(kStrCantFindURLHelper, msg);
} else {
GetCString(kStrURLHelperNotFound, fmt);
sprintf(msg, fmt, name);
}
DoCantFindHelperDialog(msg);
} else if (err == memFullErr || err == memFragErr || err == appMemFullErr) {
GetCString(kStrURLHelperNoMem, fmt);
sprintf(msg, fmt, name);
ErrorMessage(msg);
} else {
GetCString(kStrURLHelperUnexpectedErr, fmt);
sprintf(msg, fmt, err, name);
ErrorMessage(msg);
}
return userCanceledErr;
}
/*----------------------------------------------------------------------------
IsSlackMailtoURL
Check for a slack emailto URL.
Entry: text = handle to text.
begin = offset in text of beginning of object.
end = offset in text of end of object.
Exit: function result = true if slack emailto URL.
url = parsed URL.
----------------------------------------------------------------------------*/
static Boolean IsSlackMailtoURL (Handle text, short begin, short end, CStr255 url)
{
char *p, *q, *x, *y, *textEnd;
long len;
Boolean couldBeEmailAddress = false;
p = *text + begin;
q = *text + end;
textEnd = *text + MyGetHandleSize(text);
if (*p == '<') {
x = p - 1;
while (x > *text && *x != CR) x--;
if (x > *text) x++;
if (MyStrNEqual(x, "From", 4)) {
y = x+4;
} else if (MyStrNEqual(x, "Reply-To", 8)) {
y = x+8;
} else {
y = nil;
}
if (y != nil) {
while (isLWSP(*y) && y < p) y++;
if (y < p && *y == ':') couldBeEmailAddress = true;
} else {
y = q + 1;
while (y < textEnd && isLWSP(*y)) y++;
if (y + 6 <= textEnd && MyStrNEqual(y, "wrote:", 6))
couldBeEmailAddress = true;
}
if (!couldBeEmailAddress) return false;
}
x = p;
while (*x != '@' && x < q) x++;
if (x >= q) return false;
while (*x != '.' && x < q) x++;
if (x >= q) return false;
if (*p == '<') {
p++;
q--;
}
len = q - p + 1;
if (len == 0 || len > 255) return false;
BlockMoveData(p, url, len);
url[len] = 0;
return true;
}
/*----------------------------------------------------------------------------
IsSlackNewsURL
Check for a slack news URL.
Entry: text = handle to text.
begin = offset in text of beginning of object.
end = offset in text of end of object.
Exit: function result = true if slack news URL.
url = parsed URL.
----------------------------------------------------------------------------*/
static Boolean IsSlackNewsURL (Handle text, short begin, short end, CStr255 url)
{
char *p, *q, *x;
long len;
p = *text + begin;
q = *text + end;
x = p;
if (*x != '<') return false;
while (*x != '@' && x < q) x++;
if (x >= q) return false;
while (*x != '.' && x < q) x++;
if (x >= q) return false;
len = q - p + 1;
if (len == 0 || len > 255) return false;
BlockMoveData(p, url, len);
url[len] = 0;
return true;
}
/*----------------------------------------------------------------------------
ParseURL
Parse a URL.
Entry: text = handle to text.
begin = offset in text of beginning of object.
end = offset in text of end of object.
Exit: function result = kind of URL.
url = parsed URL.
*urlBegin = offset in text of beginning of parsed URL.
*urlEnd = offset in text of end of parsed URL.
*index = index in gPrefs.urlHelpers array of helper info
for parsed URL scheme, if function result = kOtherURL.
If begin == end, ParseURL tries to locate the beginning and end of
the URL.
----------------------------------------------------------------------------*/
static TURLKind ParseURL (Handle text, short begin, short end, CStr255 url,
short *urlBegin, short *urlEnd, short *index)
{
char *textEnd, *p, *q, *x, *y, *a, *b;
long len;
TURLKind urlKind;
Boolean isMultiLineURL = false, mightBeMultiLineURL = false;
short numCR;
TURLHelperInfo *helperInfo;
short i;
TURLSchemeName schemeName;
p = *text + begin;
q = *text + end - 1;
if (begin == end) {
textEnd = *text + MyGetHandleSize(text);
numCR = 0;
x = p;
y = q;
while (true) {
while (x >= *text && *x != '<' && *x != '>' &&
*x != '"' && *x != CR) x--;
if (x < *text || *x == '>' || *x == '"') break;
if (*x == CR) {
numCR++;
if (numCR > 10) break;
x--;
} else { /* *x == '<' */
a = x+1;
while (a < textEnd && isurlschemechar(*a)) a++;
mightBeMultiLineURL = a < textEnd && *a == ':';
break;
}
}
if (mightBeMultiLineURL) {
numCR = 0;
while (true) {
while (y < textEnd && *y != '<' && *y != '>' &&
*y != '"' && *y != CR) y++;
if (y >= textEnd || *y == '<' || *y == '"') break;
if (*y == CR) {
numCR++;
if (numCR > 10) break;
y++;
} else { /* *y == '>' */
isMultiLineURL = true;
break;
}
}
}
if (isMultiLineURL) {
p = x;
q = y;
} else {
while (p >= *text && !isLWSPorCR(*p) && *p != '<' && *p != '"') p--;
if (*p != '<') p++;
while (q < textEnd && !isLWSPorCR(*q) && *q != '>' && *q != '"') q++;
if (*q != '>') q--;
}
if (p >= q) return kNotURL;
}
while (p < q && isLWSPorCR(*p)) p++;
while (p < q && isLWSPorCR(*q)) q--;
if (*p == '<') {
if (*q != '>') return kNotURL;
} else {
if (*q == '>') return kNotURL;
}
*urlBegin = p - *text;
*urlEnd = q - *text + 1;
x = p;
if (*x == '<') x++;
y = x;
while (isurlschemechar(*y) && y < q) y++;
if (*y == ':') {
if (MyStrNEqual(x, "url:", 4)) x += 4;
if (*q == '>') q--;
len = q - x + 1;
if (len == 0 || len > 255) return kNotURL;
BlockMoveData(x, url, len);
url[len] = 0;
b = url;
while (*b != ':' && *b != 0) b++;
if (*b == ':') {
for (a = url; a < b; a++) *a = tolower(*a);
}
if (MyStrNEqual(url, "mailto", 6)) {
urlKind = kMailtoURL;
goto exit;
}
if (MyStrNEqual(url, "news", 4)) {
urlKind = kNewsURL;
goto exit;
}
if (MyStrNEqual(url, "nntp", 4)) {
urlKind = kNntpURL;
goto exit;
}
for (a = url, b = schemeName;
*a != 0 && *a != ':' && b < schemeName + kMaxSchemeNameLen;
a++, b++) *b = *a;
*b = 0;
MyICReadURLHelperPref(schemeName);
for (i = 0, helperInfo = *gPrefs.urlHelpers;
*helperInfo->schemeName != 0;
i++, helperInfo++)
{
if (MyStrEqual(schemeName, helperInfo->schemeName)) {
urlKind = kOtherURL;
*index = i;
goto exit;
}
}
}
begin = p - *text;
end = q - *text;
if (IsSlackMailtoURL(text, begin, end, url)) {
urlKind = kMailtoURL;
goto exit;
}
if (IsSlackNewsURL(text, begin, end, url)) {
urlKind = kNewsURL;
goto exit;
}
return kNotURL;
exit:
/* Strip line breaks and white space around line breaks from the url. */
for (p = q = url; *p != 0; p++) {
if (*p == CR) {
q--;
while (q >= url && isLWSP(*q)) q--;
q++;
p++;
while (*p != 0 && isLWSP(*p)) p++;
p--;
} else {
*q++ = *p;
}
}
*q = 0;
return urlKind;
}
/*----------------------------------------------------------------------------
ParseNntpURL
Parse an nntp URL.
Entry: url = URL string.
Exit: function result = error code (paramErr if syntax error).
host = news server host address.
*port = port number.
newsgroup = newsgroup name.
*artNumber = article number.
----------------------------------------------------------------------------*/
static OSErr ParseNntpURL (char *url, char *host, short *port,
char *newsgroup, long *artNumber)
{
char *p, *q;
p = url + strlen("nntp://");
q = host;
while (*p != ':' && *p != '/' && *p != 0) *q++ = *p++;
*q = 0;
if (*p == ':') {
*port = atoi(p+1);
while (*p != '/' && *p != 0) p++;
} else {
*port = kNNTPPort;
}
if (*p != '/') return paramErr;
p++;
q = newsgroup;
while (*p != '/' && *p != 0) *q++ = *p++;
*q = 0;
if (*p != '/') return paramErr;
*artNumber = atol(p+1);
return noErr;
}
/*----------------------------------------------------------------------------
CreateAURLFile
Create Fetch or Anarchie AURL file.
Entry: sig = signature of helper.
url = URL string.
Exit: function result = error code.
*docSpec = file spec of created AURL file.
----------------------------------------------------------------------------*/
static OSErr CreateAURLFile (OSType sig, char *url, FSSpec *docSpec)
{
OSErr err = noErr;
short fRefNum = 0, len;
char cmd[1000];
long count;
err = CreateTemporaryFile(docSpec, kNewsWatcherSignature, sig, kFTPHelperURLFileType);
if (err != noErr) return err;
err = FSpOpenDF(docSpec, fsRdWrPerm, &fRefNum);
if (err != noErr) return err;
len = strlen(url);
sprintf(cmd, "FCH %s", url);
count = strlen(cmd);
err = MyFSWriteNoCache(fRefNum, &count, cmd, nil);
MyFSClose(fRefNum, nil);
return err;
}
/*----------------------------------------------------------------------------
CreateNCSATelnetSettingsFile
Create an NCSA Telnet settings file.
Entry: url = URL string.
Exit: function result = error code.
*docSpec = file spec of created settings file.
----------------------------------------------------------------------------*/
static OSErr CreateNCSATelnetSettingsFile (char *url, FSSpec *docSpec)
{
OSErr err = noErr;
short fRefNum = 0;
char cmd[1000];
long count;
CStr255 hostName;
short port;
char *p, *q;
err = CreateTemporaryFile(docSpec, kNewsWatcherSignature,
kNCSATelnetCreatorType, kNCSATelnetSettingsFileType);
if (err != noErr) return err;
err = FSpOpenDF(docSpec, fsRdWrPerm, &fRefNum);
if (err != noErr) return err;
p = url + strlen("telnet://");
q = p;
while (*q != '@' && *q != 0) q++;
if (*q == '@') p = q+1;
q = hostName;
while (*p != ':' && *p != '/' && *p != 0) *q++ = *p++;
*q = 0;
if (*p == ':') {
port = atoi(p+1);
} else {
port = 23;
}
sprintf(cmd, "name=\"%s\"\rhost=\"%s\"\rport=\"%d\"\r", url, hostName, port);
count = strlen(cmd);
err = MyFSWriteNoCache(fRefNum, &count, cmd, nil);
MyFSClose(fRefNum, nil);
return err;
}
/*----------------------------------------------------------------------------
CreateTn3270SettingsFile
Create a tn3270 settings file.
Entry: url = URL string.
Exit: function result = error code.
*docSpec = file spec of created settings file.
----------------------------------------------------------------------------*/
static OSErr CreateTn3270SettingsFile (char *url, FSSpec *docSpec)
{
OSErr err = noErr;
short fRefNum = 0;
char cmd[1000];
long count;
CStr255 hostName;
char *p, *q;
err = CreateTemporaryFile(docSpec, kNewsWatcherSignature,
ktn3270CreatorType, ktn3270SettingsFileType);
if (err != noErr) return err;
err = FSpOpenDF(docSpec, fsRdWrPerm, &fRefNum);
if (err != noErr) return err;
p = url + strlen("tn3270://");
q = p;
while (*q != '@' && *q != 0) q++;
if (*q == '@') p = q+1;
q = hostName;
while (*p != ':' && *p != '/' && *p != 0) *q++ = *p++;
*q = 0;
sprintf(cmd, "host_name=\"%s\"\rwindow_title=\"%s\"\r", hostName, url);
count = strlen(cmd);
err = MyFSWriteNoCache(fRefNum, &count, cmd, nil);
MyFSClose(fRefNum, nil);
return err;
}
/*----------------------------------------------------------------------------
OpenHelperWithURL
Open a helper program and pass it a URL string.
Entry: helperInfo = pointer to helper info.
url = URL string.
foreground = true to run helper in foreground, false to run it
in the background.
Exit: function result = error code.
----------------------------------------------------------------------------*/
static OSErr OpenHelperWithURL (TURLHelperInfo *helperInfo, CStr255 url,
Boolean foreground)
{
OSErr err = noErr;
ProcessSerialNumber psn;
FSSpec appSpec, docSpec;
Boolean running;
unsigned short launchControlFlags;
char hackedURL[500];
unsigned long lastMod;
launchControlFlags = launchContinue | launchNoFileFlags | launchUseMinimum;
if (!foreground) launchControlFlags |= launchDontSwitch;
err = FindAppFromSig(helperInfo->sig, &appSpec, &running, &psn);
if (err != noErr) goto exit;
err = GetLastModDateTime(&appSpec, &lastMod);
if (err != noErr) goto exit;
if (lastMod != helperInfo->lastMod) {
if (!ValidURLHelper(&appSpec, helperInfo)) return userCanceledErr;
}
switch (helperInfo->sig) {
case kFetchCreatorType:
if (helperInfo->versionNumber >= kFetchMinVersionNumberGURL) {
err = LaunchAppWithEventAndString(running, &appSpec, &psn,
kGetURLEventClass, kGetURLEventID,
keyDirectObject, url, 0, launchControlFlags);
} else {
err = CreateAURLFile(helperInfo->sig, url, &docSpec);
if (err != noErr) goto exit;
err = LaunchAppWithDoc(running, &appSpec, &psn, &docSpec, 0,
launchControlFlags);
}
break;
case kAnarchieCreatorType:
if (helperInfo->versionNumber >= kAnarchieMinVersionNumberGURL) {
err = LaunchAppWithEventAndString(running, &appSpec, &psn,
kGetURLEventClass, kGetURLEventID,
keyDirectObject, url, 0, launchControlFlags);
} else {
err = CreateAURLFile(helperInfo->sig, url, &docSpec);
if (err != noErr) goto exit;
err = LaunchAppWithDoc(running, &appSpec, &psn, &docSpec, 0,
launchControlFlags);
}
break;
case kMacWebCreatorType:
if (helperInfo->versionNumber >= kMacWebMinVersionNumberGURL) {
err = LaunchAppWithEventAndString(running, &appSpec, &psn,
kGetURLEventClass, kGetURLEventID,
keyDirectObject, url, 0, launchControlFlags);
} else {
err = LaunchAppWithEventAndString(running, &appSpec, &psn,
kMacWebAndWAISOpenURLEventClass, kMacWebAndWAISOpenURLEventID,
kMacWebAndWAISOpenURLKeyword, url, 0, launchControlFlags);
}
break;
case kMacWAISCreatorType:
err = LaunchAppWithEventAndString(running, &appSpec, &psn,
kMacWebAndWAISOpenURLEventClass, kMacWebAndWAISOpenURLEventID,
kMacWebAndWAISOpenURLKeyword, url, 0, launchControlFlags);
break;
case kNCSAMosaicCreatorType:
err = LaunchAppWithEventAndString(running, &appSpec, &psn,
kNCSAMosaicOpenURLEventClass, kNCSAMosaicOpenURLEventID,
keyDirectObject, url, 0, launchControlFlags);
break;
case kTurboGopherCreatorType:
if (helperInfo->versionNumber < kTurboGopherMinVersionNumberCanon) {
/* Special case for version 2.0a1 - must add <URL:...> wrapper. */
if (MyStrNEqual(url, "<URL:", 5)) {
strcpy(hackedURL, url);
} else if (MyStrNEqual(url, "URL:", 4)) {
sprintf(hackedURL, "<%s>", url);
} else {
sprintf(hackedURL, "<URL:%s>", url);
}
err = LaunchAppWithEventAndString(running, &appSpec, &psn,
kGetURLEventClass, kGetURLEventID,
keyDirectObject, hackedURL, 0, launchControlFlags);
} else {
/* Versions >= 2.0b1 accept canoncial form without wrapper. */
err = LaunchAppWithEventAndString(running, &appSpec, &psn,
kGetURLEventClass, kGetURLEventID,
keyDirectObject, url, 0, launchControlFlags);
}
break;
case kNCSATelnetCreatorType:
if (helperInfo->versionNumber >= kNCSATelnetMinVersionNumberGURL) {
err = LaunchAppWithEventAndString(running, &appSpec, &psn,
kGetURLEventClass, kGetURLEventID,
keyDirectObject, url, 0, launchControlFlags);
} else {
err = CreateNCSATelnetSettingsFile(url, &docSpec);
if (err != noErr) goto exit;
err = LaunchAppWithDoc(running, &appSpec, &psn, &docSpec, 0,
launchControlFlags);
}
break;
case ktn3270CreatorType:
err = CreateTn3270SettingsFile(url, &docSpec);
if (err != noErr) goto exit;
err = LaunchAppWithDoc(running, &appSpec, &psn, &docSpec, 0,
launchControlFlags);
break;
default:
err = LaunchAppWithEventAndString(running, &appSpec, &psn,
kGetURLEventClass, kGetURLEventID,
keyDirectObject, url, 0, launchControlFlags);
break;
}
if (err != noErr) goto exit;
return noErr;
exit:
return HelperErrorMessage(err, helperInfo->sig);
}
/*----------------------------------------------------------------------------
FlashObject
Flash an object in an article, message, or text window.
Entry: theTE = handle to TextEdit record.
start = starting position of object.
end = ending position of object.
----------------------------------------------------------------------------*/
static void FlashObject (TEHandle theTE, short start, short end)
{
short i;
long finalTick;
TESetSelect(start, end, theTE);
for (i = 0; i < 2; i++) {
Delay(5, &finalTick);
TEDeactivate(theTE);
Delay(5, &finalTick);
TEActivate(theTE);
}
}
/*----------------------------------------------------------------------------
ProcessURLEscapeCodes
Process "%xx" escape codes in a URL string (replace them by the characters
they represent).
Entry: url = URL with escape codes.
Exit: url = URL with escape codes replaced by the characters they
represent.
----------------------------------------------------------------------------*/
static void ProcessURLEscapeCodes (char *url)
{
char *p, *q;
char c1, c2;
p = q = url;
while (*p != 0) {
if (*p == '%') {
c1 = tolower(*(p+1));
c2 = tolower(*(p+2));
if (isxdigit(c1) && isxdigit(c2)) {
c1 = isdigit(c1) ? c1 - '0' : c1 - 'a' + 10;
c2 = isdigit(c2) ? c2 - '0' : c2 - 'a' + 10;
*q++ = (c1 << 4) + c2;
p += 3;
} else {
*q++ = *p++;
}
} else {
*q++ = *p++;
}
}
*q = 0;
}
/*----------------------------------------------------------------------------
OpenNewsURL
Open a news URL or slack URL.
Entry: url = news URL or slack URL.
Exit: function result = error code (fnfErr if article or group not found).
----------------------------------------------------------------------------*/
static OSErr OpenNewsURL (char *url)
{
char *p;
long item;
Boolean hasArts;
if (MyStrNEqual(url, "news:", 5)) {
if (url[5] == '*' && url[6] == 0) {
/* Show full group list window */
if (((WindowPeek)gFullGroupWindow)->visible) {
MySelectWindow(gFullGroupWindow);
return noErr;
} else {
return DoShowHideFullGroupList();
}
} else {
for (p = url; *p != 0; p++)
if (*p == '@')
return OpenReferencedArticle(url);
/* Open group from full group list */
item = FindGroupIndex(url+5);
if (item == -1) return fnfErr;
return OpenGroupItem(gFullGroupWindow, item, gPrefs.maxFetch, &hasArts);
}
} else {
return OpenReferencedArticle(url);
}
return noErr;
}
/*----------------------------------------------------------------------------
OpenURL
Open a URL.
Entry: wind = pointer to article, message, or text window.
theTE = handle to text edit record.
begin = offset in text of beginning of url.
end = offset in text of end of url.
Exit: function result = error code.
----------------------------------------------------------------------------*/
static OSErr OpenURL (WindowPtr wind, TEHandle theTE, short begin, short end)
{
TWindow **info;
CStr255 url, msg;
short urlBegin, urlEnd;
TURLKind urlKind;
TURLHelperInfo helperInfo;
Boolean foreground, isFTP;
CStr255 host, newsgroup;
short port, index, len;
long artNumber;
OSErr err = noErr;
TURLSchemeName schemeName;
short i;
TURLHelperInfo *p;
info = (TWindow**)GetWRefCon(wind);
urlKind = ParseURL((**theTE).hText, begin, end, url, &urlBegin, &urlEnd, &index);
if (urlKind == kNotURL) {
SysBeep(0);
return userCanceledErr;
}
FlashObject(theTE, urlBegin, urlEnd);
switch (urlKind) {
case kMailtoURL:
ProcessURLEscapeCodes(url);
return OpenMailWindow(url);
case kNewsURL:
ProcessURLEscapeCodes(url);
err = OpenNewsURL(url);
if (err == fnfErr) {
NoteMessageNumber(kStrArticleOrGroupNotFound);
return userCanceledErr;
} else {
return err;
}
case kNntpURL:
ProcessURLEscapeCodes(url);
err = ParseNntpURL(url, host, &port, newsgroup, &artNumber);
if (err == paramErr) {
ErrorMessageNumber(kStrNntpURLSyntaxError);
return userCanceledErr;
} else if (err != noErr) {
return err;
}
err = OpenArticleOnAnyServer(host, port, newsgroup, artNumber);
if (err == fnfErr) {
NoteMessageNumber(kStrArticleOrGroupNotFound);
return userCanceledErr;
} else {
return err;
}
case kOtherURL:
strcpy(schemeName, (*gPrefs.urlHelpers)[index].schemeName);
isFTP = MyStrEqual(schemeName, "ftp");
if (isFTP && gPrefs.useWebHelperForHtmlFiles) {
len = strlen(url);
if ((len > 5 && MyStrNEqual(url + len - 5, ".html", 5)) ||
(len > 4 && MyStrNEqual(url + len - 4, ".htm", 4)))
{
for (i = 0, p = *gPrefs.urlHelpers; *p->schemeName != 0; i++, p++)
{
if (MyStrEqual("http", p->schemeName)) {
index = i;
strcpy(schemeName, "http");
isFTP = false;
break;
}
}
}
}
InitDefaultURLHelper(schemeName);
helperInfo = (*gPrefs.urlHelpers)[index];
foreground = !isFTP || url[strlen(url)-1] == '/';
if (helperInfo.sig != 0) {
err = OpenHelperWithURL(&helperInfo, url, foreground);
(*gPrefs.urlHelpers)[index] = helperInfo;
return err;
} else {
GetCString(kStrCantFindDefaultURLHelper, msg);
DoCantFindHelperDialog(msg);
return userCanceledErr;
}
}
return err;
}
/*----------------------------------------------------------------------------
CommandClick
Handle a command-click in an article, message, or text window.
Entry: wind = pointer to article, message, or text window.
theTE = handle to TextEdit record in which command-click occurred.
oldSelStart = start of selection range before user command-clicked.
oldSelEnd = end of selection range before user command-clicked.
modifiers = modifiers field from event record.
Exit: function result = error code.
----------------------------------------------------------------------------*/
OSErr CommandClick (WindowPtr wind, TEHandle theTE,
short oldSelStart, short oldSelEnd, short modifiers)
{
TWindow **info;
short selStart, selEnd, begin, end;
DialogPtr dlg;
short item;
OSErr err = noErr;
if (!gStartupOK) return noErr;
if ((modifiers & optionKey) == 0 && (modifiers & cmdKey) == 0) return noErr;
if ((modifiers & cmdKey) == 0) {
err = MyGetNewDialog(kOptionClickDlg, ok, cancel, &dlg);
SysBeep(0);
if (err != noErr) return err;
MyModalDialog(dlg, gDialogFilterUPP, &item);
DoClose(dlg);
if (item == cancel) return userCanceledErr;
}
info = (TWindow**)GetWRefCon(wind);
selStart = (**theTE).selStart;
selEnd = (**theTE).selEnd;
if (selStart != selEnd) return noErr;
if (oldSelStart < oldSelEnd && oldSelStart <= selStart &&
selStart <= oldSelEnd)
{
begin = oldSelStart;
end = oldSelEnd;
} else {
begin = end = selStart;
}
return OpenURL(wind, theTE, begin, end);
}
/*----------------------------------------------------------------------------
DoOpenURL
Handle the "Open URL" command.
Entry: wind = pointer to article, message, or text window.
Exit: function result = error code.
----------------------------------------------------------------------------*/
OSErr DoOpenURL (WindowPtr wind)
{
TWindow **info;
TEHandle theTE;
TMsgFieldInfo **fields;
short curField;
info = (TWindow**)GetWRefCon(wind);
if ((**info).kind == kMessage) {
fields = (**info).fields;
curField = (**info).curField;
theTE = (*fields)[curField].edit;
} else {
theTE = (**info).theTE;
}
return OpenURL(wind, theTE, (**theTE).selStart, (**theTE).selEnd);
}
/*----------------------------------------------------------------------------
OpenURLString
Open a news, nntp, or mailto URL.
Entry: urlString = the URL string.
Exit: function result = error code.
----------------------------------------------------------------------------*/
OSErr OpenURLString (char *urlString)
{
OSErr err = noErr;
Handle h = nil;
short len, urlBegin, urlEnd;
CStr255 url;
TURLKind urlKind;
CStr255 host, newsgroup;
short port;
long artNumber;
short index;
len = strlen(urlString);
err = MyPtrToHand(urlString, &h, len);
if (err != noErr) return err;
urlKind = ParseURL(h, 0, len, url, &urlBegin, &urlEnd, &index);
MyDisposeHandle(h);
ProcessURLEscapeCodes(url);
switch (urlKind) {
case kMailtoURL:
return OpenMailWindow(url);
case kNewsURL:
return OpenNewsURL(url);
case kNntpURL:
err = ParseNntpURL(url, host, &port, newsgroup, &artNumber);
if (err != noErr) return err;
return OpenArticleOnAnyServer(host, port, newsgroup, artNumber);
default:
return paramErr;
}
}